package risoe.syslab.fhnew.controller;

/*
 * Copyright (c) 2012-2014, Technical University of Denmark (DTU)
 * All rights reserved.
 * 
 * The Flexhouse 2.0 platform ("this software") is licensed under the
 * BSD 3-clause license.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   - Neither the name of DTU nor the names of its contributors may be used to
 *     endorse or promote products derived from this software without specific
 *     prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE TECHNICAL UNIVERSITY OF DENMARK BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.concurrent.ConcurrentHashMap;

import risoe.syslab.comm.shared.FunctionNotSupportedException;
import risoe.syslab.comm.typebased.interfaces.SmartBuilding;
import risoe.syslab.config.CommonDeviceConfig;
import risoe.syslab.fhnew.common.SystemConstants.DeviceType;
import risoe.syslab.fhnew.config.BuildingConfig;
import risoe.syslab.model.CompositeMeasurement;
import risoe.syslab.model.CompositeString;
import risoe.syslab.model.flexhouse.ActuatorDescription;
import risoe.syslab.model.flexhouse.ControllerParameter;
import risoe.syslab.model.flexhouse.EndpointDescription;
import risoe.syslab.model.flexhouse.SensorDescription;
import risoe.syslab.model.iec61850.DELV;
import risoe.syslab.model.iec61850.GPSL;
import risoe.syslab.model.iec61850.HLTH;
import risoe.syslab.model.iec61850.LNPL;
import risoe.syslab.model.iec61850.PNPL;
import risoe.syslab.model.iec61850.WYEA;
import risoe.syslab.model.iec61850.WYEV;

public class SmartBuildingInterface implements SmartBuilding{
	public static final int SYSLAB_TIMEOUT_MINUTES = 5;
	
	private ControllerManager mControllerManager;
	private BuildingConfig mBuildingConfig;
	private ConcurrentHashMap<ControllerParameter, CompositeString> mControllerParameters;
	private ConcurrentHashMap<DeviceType, DefaultController> mActiveDefaultControllerMap;
	private ConcurrentHashMap<String, ControllerConfig> mControllerConfigMap;
	
	public SmartBuildingInterface(ControllerManager manager, BuildingConfig config, 
			ConcurrentHashMap<DeviceType, DefaultController> defaultControllerMap,
			ConcurrentHashMap<String, ControllerConfig> configMap,
			ConcurrentHashMap<ControllerParameter, CompositeString> controllerParameters) {
		mControllerManager = manager;
		mBuildingConfig = config;
		mActiveDefaultControllerMap = defaultControllerMap;
		mControllerConfigMap = configMap;
		mControllerParameters = controllerParameters;
	}
	
	Hashtable<String, Integer> mPowerMeasurementPoints = new Hashtable<String, Integer>();
	{
		mPowerMeasurementPoints.put("Main", 0);
		mPowerMeasurementPoints.put("Auxilliary", 1);
	}

	/**
	 * Convenience method for getting a combined aux measurement
	 */
	private double getAuxValue(String channelName){
		double result = 0;
		for (int i=1; i<4; i++){
			result = mControllerManager.getSensorValueInternal("Aux_"+channelName+""+(i++)).value;
		}
		return result;
	}

	@Override
	public Hashtable<String, Integer> getPowerMeasurementPoints()
			throws RemoteException {
		return mPowerMeasurementPoints;
	}

	@Override
	public CompositeMeasurement getActivePower(int point)
			throws RemoteException {
		if (point == 0){
			return mControllerManager.getSensorValueInternal("Grid_Psum");
		}
		else if (point == 1){
			return new CompositeMeasurement(getAuxValue("P"));
		}
		else throw new IllegalArgumentException("Invalid point argument. Use getPowerMeasurementPoints() to query valid points");
	}

	@Override
	public WYEV getPhaseActivePower(int point) throws RemoteException {
		if (point == 0){
			return new WYEV(mControllerManager.getSensorValueInternal("Grid_P1"), mControllerManager.getSensorValueInternal("Grid_P2"), mControllerManager.getSensorValueInternal("Grid_P3"));
		}
		else if (point == 1){
			return new WYEV(mControllerManager.getSensorValueInternal("Aux_P1"), mControllerManager.getSensorValueInternal("Aux_P2"), mControllerManager.getSensorValueInternal("Aux_P3"));
		}
		else throw new IllegalArgumentException("Invalid point argument. Use getPowerMeasurementPoints() to query valid points");
	}

	@Override
	public CompositeMeasurement getReactivePower(int point)
			throws RemoteException {
		if (point == 0){
			return mControllerManager.getSensorValueInternal("Grid_Qsum");
		}
		else if (point == 1){
			return new CompositeMeasurement(getAuxValue("Q"));
		}
		else throw new IllegalArgumentException("Invalid point argument. Use getPowerMeasurementPoints() to query valid points");
	}

	@Override
	public WYEV getPhaseReactivePower(int point) throws RemoteException {
		if (point == 0){
			return new WYEV(mControllerManager.getSensorValueInternal("Grid_Q1"), mControllerManager.getSensorValueInternal("Grid_Q2"), mControllerManager.getSensorValueInternal("Grid_Q3"));
		}
		else if (point == 1){
			return new WYEV(mControllerManager.getSensorValueInternal("Aux_Q1"), mControllerManager.getSensorValueInternal("Aux_Q2"), mControllerManager.getSensorValueInternal("Aux_Q3"));
		}
		else throw new IllegalArgumentException("Invalid point argument. Use getPowerMeasurementPoints() to query valid points");
	}

	@Override
	public CompositeMeasurement getApparentPower(int point)
			throws RemoteException {
		if (point == 0){
			return mControllerManager.getSensorValueInternal("Grid_Ssum");
		}
		else if (point == 1){
			return new CompositeMeasurement(getAuxValue("S"));
		}
		else throw new IllegalArgumentException("Invalid point argument. Use getPowerMeasurementPoints() to query valid points");
	}

	@Override
	public WYEV getPhaseApparentPower(int point) throws RemoteException {
		if (point == 0){
			return new WYEV(mControllerManager.getSensorValueInternal("Grid_S1"), mControllerManager.getSensorValueInternal("Grid_S2"), mControllerManager.getSensorValueInternal("Grid_S3"));
		}
		else if (point == 1){
			return new WYEV(mControllerManager.getSensorValueInternal("Aux_S1"), mControllerManager.getSensorValueInternal("Aux_S2"), mControllerManager.getSensorValueInternal("Aux_S3"));
		}
		else throw new IllegalArgumentException("Invalid point argument. Use getPowerMeasurementPoints() to query valid points");
	}

	@Override
	public CompositeMeasurement getPowerFactor(int point)
			throws RemoteException {
		if (point == 0){
			return mControllerManager.getSensorValueInternal("Grid_PFavg");
		}
		else if (point == 1){
			return new CompositeMeasurement(getAuxValue("PF"));
		}
		else throw new IllegalArgumentException("Invalid point argument. Use getPowerMeasurementPoints() to query valid points");
	}

	@Override
	public WYEV getPhasePowerFactor(int point) throws RemoteException {
		if (point == 0){
			return new WYEV(mControllerManager.getSensorValueInternal("Grid_PF1"), mControllerManager.getSensorValueInternal("Grid_PF2"), mControllerManager.getSensorValueInternal("Grid_PF3"));
		}
		else if (point == 1){
			return new WYEV(mControllerManager.getSensorValueInternal("Aux_PF1"), mControllerManager.getSensorValueInternal("Aux_PF2"), mControllerManager.getSensorValueInternal("Aux_PF3"));
		}
		else throw new IllegalArgumentException("Invalid point argument. Use getPowerMeasurementPoints() to query valid points");
	}

	@Override
	public CompositeMeasurement getFrequency(int point) throws RemoteException {
		if (point == 0){
			return mControllerManager.getSensorValueInternal("Grid_f");
		}
		else if (point == 1){
			return new CompositeMeasurement(getAuxValue("f")/3.0);
		}
		else throw new IllegalArgumentException("Invalid point argument. Use getPowerMeasurementPoints() to query valid points");
	}

	@Override
	public CompositeMeasurement getPhaseVoltage(int point)
			throws RemoteException {
		if (point == 0){
			return mControllerManager.getSensorValueInternal("Grid_Uphavg");
		}
		else if (point == 1){
			return new CompositeMeasurement(getAuxValue("Volt")/3.0);
		}
		else throw new IllegalArgumentException("Invalid point argument. Use getPowerMeasurementPoints() to query valid points");
	}

	@Override
	public WYEV getPhaseVoltages(int point) throws RemoteException {
		if (point == 0){
			return new WYEV(mControllerManager.getSensorValueInternal("Grid_Volt1"), mControllerManager.getSensorValueInternal("Grid_Volt2"), mControllerManager.getSensorValueInternal("Grid_Volt3"));
		}
		else if (point == 1){
			return new WYEV(mControllerManager.getSensorValueInternal("Aux_Volt1"), mControllerManager.getSensorValueInternal("Aux_Volt2"), mControllerManager.getSensorValueInternal("Aux_Volt3"));
		}
		else throw new IllegalArgumentException("Invalid point argument. Use getPowerMeasurementPoints() to query valid points");
	}

	@Override
	public CompositeMeasurement getInterphaseVoltage(int point)
			throws RemoteException {
		if (point == 0){
			return mControllerManager.getSensorValueInternal("Grid_Uavg");
		}
		else if (point == 1){
			//TODO: Unsure about implementation
			return null;
		}
		else throw new IllegalArgumentException("Invalid point argument. Use getPowerMeasurementPoints() to query valid points");
	}

	@Override
	public DELV getInterphaseVoltages(int point) throws RemoteException {
		if (point == 0){
			return new DELV(mControllerManager.getSensorValueInternal("Grid_Volt12"), mControllerManager.getSensorValueInternal("Grid_Volt23"), mControllerManager.getSensorValueInternal("Grid_Volt31"));
		}
		else if (point == 1){
			//TODO: Unsure about implementation
			return null;
		}
		else throw new IllegalArgumentException("Invalid point argument. Use getPowerMeasurementPoints() to query valid points");
	}

	@Override
	public CompositeMeasurement getPhaseCurrent(int point)
			throws RemoteException {
		if (point == 0){
			return mControllerManager.getSensorValueInternal("Grid_Ampavg");
		}
		else if (point == 1){
			return new CompositeMeasurement(getAuxValue("Amp")/3.0);
		}
		else throw new IllegalArgumentException("Invalid point argument. Use getPowerMeasurementPoints() to query valid points");
	}

	@Override
	public CompositeMeasurement getNeutralCurrent(int point)
			throws RemoteException {
		// TODO: Unsure about implementation
		return null;
	}

	@Override
	public WYEA getPhaseCurrents(int point) throws RemoteException {
		// TODO: Unsure about implementation
		return null;
	}

	@Override
	public CompositeMeasurement getActiveEnergyImport(int point)
			throws RemoteException {
		if (point == 0){
			return mControllerManager.getSensorValueInternal("Grid_Pimp");
		}
		else if (point == 1){
			// TODO: Should these be treated as an average or added?
			return new CompositeMeasurement(getAuxValue("Pimp")/3.0);
		}
		else throw new IllegalArgumentException("Invalid point argument. Use getPowerMeasurementPoints() to query valid points");
	}

	@Override
	public CompositeMeasurement getActiveEnergyExport(int point)
			throws RemoteException {
		if (point == 0){
			return mControllerManager.getSensorValueInternal("Grid_Pexp");
		}
		else if (point == 1){
			// TODO: Should these be treated as an average or added?
			return new CompositeMeasurement(getAuxValue("Pexp")/3.0);
		}
		else throw new IllegalArgumentException("Invalid point argument. Use getPowerMeasurementPoints() to query valid points");
	}

	@Override
	public CompositeMeasurement getReactiveEnergyImport(int point)
			throws RemoteException {
		if (point == 0){
			return mControllerManager.getSensorValueInternal("Grid_Qimp");
		}
		else if (point == 1){
			// TODO: Should these be treated as an average or added?
			return new CompositeMeasurement(getAuxValue("Qimp")/3.0);
		}
		else throw new IllegalArgumentException("Invalid point argument. Use getPowerMeasurementPoints() to query valid points");
	}

	@Override
	public CompositeMeasurement getReactiveEnergyExport(int point)
			throws RemoteException {
		if (point == 0){
			return mControllerManager.getSensorValueInternal("Grid_Qexp");
		}
		else if (point == 1){
			// TODO: Should these be treated as an average or added?
			return new CompositeMeasurement(getAuxValue("Qexp")/3.0);
		}
		else throw new IllegalArgumentException("Invalid point argument. Use getPowerMeasurementPoints() to query valid points");
	}

	@Override
	public CommonDeviceConfig getNodeConfiguration() throws RemoteException {
		// TODO: Implement?
		throw new FunctionNotSupportedException("Function Not Implemented");
	}

	@Override
	public String getBuildingName() throws RemoteException {
		return mBuildingConfig.getBuildingName();
	}

	@Override
	public LNPL getBuildingLogicalNameplate() throws RemoteException {
		throw new FunctionNotSupportedException("Function Not Implemented");
	}

	@Override
	public PNPL getBuildingPhysicalNameplate() throws RemoteException {
		throw new FunctionNotSupportedException("Function Not Implemented");
	}

	@Override
	public HLTH getBuildingHealth() throws RemoteException {
		return new HLTH(HLTH.HEALTH_OK);
	}

	@Override
	public GPSL getGPSLocation() throws RemoteException {
		return new GPSL(mBuildingConfig.getLongitude(), mBuildingConfig.getLatitude(), mBuildingConfig.getAltitude());
	}

	@Override
	public double getRatedP() throws RemoteException {
		throw new FunctionNotSupportedException("Function Not Implemented");
	}

	@Override
	public double getRatedQ() throws RemoteException {
		throw new FunctionNotSupportedException("Function Not Implemented");
	}

	@Override
	public double getRatedS() throws RemoteException {
		throw new FunctionNotSupportedException("Function Not Implemented");
	}

	@Override
	public double getRatedU() throws RemoteException {
		throw new FunctionNotSupportedException("Function Not Implemented");
	}

	@Override
	public double getRatedf() throws RemoteException {
		throw new FunctionNotSupportedException("Function Not Implemented");
	}

	@Override
	public String[] getInstalledControllers() throws RemoteException {
		return mControllerConfigMap.keySet().toArray(new String[0]);
	}

	@Override
	public String[] getActiveControllers() throws RemoteException {
		ArrayList<String> activeControllers = new ArrayList<String>();
		for (DefaultController cont : mActiveDefaultControllerMap.values()){
			activeControllers.add(cont.getClass().getSimpleName());
		}
		activeControllers.addAll(mControllerConfigMap.keySet());

		return activeControllers.toArray(new String[0]);
	}

	@Override
	public ControllerParameter[] getSupportedControllerParameters()
			throws RemoteException {
		return mControllerParameters.keySet().toArray(new ControllerParameter[0]);
	}

	@Override
	public ControllerParameter[] getSupportedParameters(String controller)
			throws RemoteException {
		// Try plugin controllers first
		ControllerConfig cont = mControllerConfigMap.get(controller);
		if (cont != null){
			return cont.getControllerParameters().toArray(new ControllerParameter[0]);
		}

		// default controllers
		for (DefaultController defController : mActiveDefaultControllerMap.values()){
			if (defController.getClass().getSimpleName().equals(controller)){
				return defController.getControllerParameters().toArray(new ControllerParameter[0]);
			}
		}

		// Error
		throw new IllegalArgumentException("No controller found for name "+controller);
	}

	@Override
	public ControllerParameter[] getSupportingControllers(String parameter)
			throws RemoteException {
		ArrayList<ControllerParameter> supportingControllers = new ArrayList<ControllerParameter>();
		for (DefaultController cont : mActiveDefaultControllerMap.values()){
			for (ControllerParameter param : cont.getControllerParameters()){
				if (param.getParameterName().equals(parameter)){
					// Match
					supportingControllers.add(param);
					break;
				}
			}
		}
		for (ControllerConfig conf : mControllerConfigMap.values()){
			for (ControllerParameter param : conf.getControllerParameters()){
				if (param.getParameterName().equals(parameter)){
					// Match
					supportingControllers.add(param);
					break;
				}
			}
		}

		return supportingControllers.toArray(new ControllerParameter[0]);
	}

	@Override
	public CompositeString[] getControllerParameterValue(String parameter)
			throws RemoteException {
		ArrayList<CompositeString> parameters = new ArrayList<CompositeString>();
		for (ControllerParameter param : mControllerParameters.keySet()){
			if (param.getParameterName().equals(parameter)){
				parameters.add(mControllerParameters.get(param));
			}
		}
		return parameters.toArray(new CompositeString[0]);
	}

	@Override
	public void setControllerParameterValue(String parameter,
			CompositeString value) throws RemoteException {
		for (ControllerParameter param : mControllerParameters.keySet()){
			if (param.getParameterName().equals(parameter)){
				// Match
				for (ControllerConfig config : mControllerConfigMap.values()){
					config.filter(param, value);
				}

				// Save this as the most recent parameter value
				mControllerParameters.put(param, value);
			}
		}
	}

	@Override
	public SensorDescription[] getSensors() throws RemoteException {
		return mBuildingConfig.getSensorDescriptions().toArray(new SensorDescription[0]);
	}

	@Override
	public ActuatorDescription[] getActuators() throws RemoteException {
		return mBuildingConfig.getActuatorDescriptions().toArray(new ActuatorDescription[0]);
	}

	@Override
	public SensorDescription[] getSensorsbyBuilding(String building)
			throws RemoteException {
		// TODO Abstract the building
		throw new FunctionNotSupportedException("Function Not Implemented");
	}

	@Override
	public ActuatorDescription[] getActuatorsbyBuilding(String building)
			throws RemoteException {
		// TODO Abstract the building
		throw new FunctionNotSupportedException("Function Not Implemented");
	}

	@Override
	public SensorDescription[] getSensorsByRoom(String room) throws RemoteException {
		ArrayList<SensorDescription> sensorsInRoom = new ArrayList<SensorDescription>();
		for (EndpointDescription sensor : mBuildingConfig.getSensorDescriptions()){
			if (sensor.getRoom().equals(room)){
				sensorsInRoom.add((SensorDescription) sensor);
			}
		}
		return sensorsInRoom.toArray(new SensorDescription[0]);
	}

	@Override
	public ActuatorDescription[] getActuatorsByRoom(String room)
			throws RemoteException {
		ArrayList<ActuatorDescription> actuatorsInRoom = new ArrayList<ActuatorDescription>();
		for (EndpointDescription actuator : mBuildingConfig.getActuatorDescriptions()){
			if (actuator.getRoom().equals(room)){
				actuatorsInRoom.add((ActuatorDescription) actuator);
			}
		}
		return actuatorsInRoom.toArray(new ActuatorDescription[0]);
	}

	@Override
	public SensorDescription[] getSensorsByType(String type) throws RemoteException {
		ArrayList<SensorDescription> sensors = new ArrayList<SensorDescription>();
		for (EndpointDescription sensor : mBuildingConfig.getSensorDescriptions()){
			if (sensor.getType().equals(type)){
				sensors.add((SensorDescription) sensor);
			}
		}
		return sensors.toArray(new SensorDescription[0]);
	}

	@Override
	public ActuatorDescription[] getActuatorsByType(String type)
			throws RemoteException {
		ArrayList<ActuatorDescription> actuators = new ArrayList<ActuatorDescription>();
		for (EndpointDescription actuator : mBuildingConfig.getActuatorDescriptions()){
			if (actuator.getType().equals(type)){
				actuators.add((ActuatorDescription) actuator);
			}
		}
		return actuators.toArray(new ActuatorDescription[0]);
	}

	@Override
	public CompositeMeasurement getValueBySensorName(String name)
			throws RemoteException {
		return mControllerManager.getSensorValueInternal(name);
	}

	@Override
	public CompositeMeasurement getSetpointByActuatorName(String name)
			throws RemoteException {
		return mControllerManager.getActuatorSetpointInternal(name);
	}

	@Override
	public void setValueByActuatorName(String name, CompositeMeasurement value) throws RemoteException {
		mControllerManager.setActuatorSyslab(name, value);
	}
}
